package org.springframework.aop.framework.autoproxy;

import java.util.List;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.Order;

/**
 * aop 1-3 @Aspect 与 advisor
 * - 切面执行顺序由 order 接口定义; order注解要加在类上
 *
 * @author H
 * @version 1.0
 * @date 2024-01-08 17:16
 */
@Slf4j
public class TestAspect {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("TAspect", TAspect.class);
        context.registerBean("AdvisorConfig", AdvisorConfig.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        /**
         *  AnnotationAwareAspectJAutoProxyCreator是bean后处理器
         *  1.找到所有切面，将高级切面转换成低级切面
         *  2.根据切面生成代理类
         */
        context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
        context.refresh();

        /**
         * findEligibleAdvisors 方法，找到所有切面；高级切面会转换成低级切面计入
         */
        AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        // 获取能够配合 Target1 使用的切面；参数2是bean名字，方便从容器中找
        List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
        advisors.forEach(t -> log.info("目标类的所有Advisor切面{}", t));

        /**
         * wrapIfNecessary 判断是否需要创建代理
         * 内部调用了 findEligibleAdvisors 方法
         */
        Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
        log.info("目标对象代理创建：{}", o1.getClass());
        Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
        log.info("目标对象代理创建：{}", o2.getClass());

        Integer result = ((Target1) o1).testAspect();
        log.info("代理执行结果：{}", result);

        context.close();

    }

    @Aspect
    @Order(1)
    static class TAspect {

        /**
         * 动态通知调用 InterceptorAndDynamicMethodMatcher ；有参数绑定时需要通知及切点，切点对参数进行匹配及绑定
         * 如@Before("execution(* testAspect())&&args(x)")
         */
        @Before("execution(* testAspect())")
        public void before() {
            log.info("@Aspect注解before");
        }

        @After("execution(* testAspect())")
        public void after() {
            log.info("@Aspect注解after");
        }

    }

    /**
     * 低等级切面
     */
    @Configuration
    static class AdvisorConfig {
        @Bean
        public Advisor deafultAdvisor(MethodInterceptor methodInterceptor) {
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* testAspect())");
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, methodInterceptor);
            // 切面顺序定义
            advisor.setOrder(2);
            return advisor;
        }

        @Bean
        public MethodInterceptor methodInterceptor() {
            return new MethodInterceptor() {
                @Override
                public Object invoke(MethodInvocation invocation) throws Throwable {
                    log.info("advisor通知methodInterceptor before...");
                    Object result = invocation.proceed();
                    log.info("advisor通知methodInterceptor after...");
                    return result;
                }
            };
        }
    }


    static class Target1 {
        public Integer testAspect() {
            log.info("Target1 testAspect主流程运行...");
            return 1;
        }
    }

    static class Target2 {
        public void testAspect2() {
            log.info("Target2 testAspect2主流程运行...");
        }
    }

}
